Python 版本更新总结!

3.8

Python 3.8 最终版将在 2019-09-30 发布,目前 380a4 版本已经确定下来,我们可以看一下新版本带来的新特性。python-380a4 下载 地址

海象运算符

walrus operator 是3.8 中的新语法 PEP-0527,有点类似 go 语言中的语法,示例如下:在求 a 长度的时候,就将值赋予 n,并且 n 做为 if 判断的条件,且在下面的作用域中还有值。

1
2
if (n := len(a)) > 10:
print(f"List is too long ({n} elements, expected <= 10)")

读取文件内容并且循环输出:

1
2
3
fp = open("test.txt")
while (line := fp.readline()):
print(line)

位置参数和关键字参数

PEP-0570

1
2
3
4
5
6
7
8
9
10
11
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)

以上定义了四个函数,standard_arg 是一个普通的函数,可以有两种方式进行参数的传递

1
2
>>> standard_arg(arg=2)
>>> standard_arg(2)

第二个函数 pos_only_arg 仅限于使用位置参数,因为函数定义中有一个/

1
2
3
4
5
6
7
>>> pos_only_arg(1)
1

>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'

第三个函数 kwd_only_args 只允许使用关键字参数 ,因为有一个 *

1
2
3
4
5
6
7
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given

>>> kwd_only_arg(arg=3)
3

最后一个函数在同一个函数定义中使用所有三个调用约定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given

>>> combined_example(1, 2, kwd_only=3)
1 2 3

>>> combined_example(1, standard=2, kwd_only=3)
1 2 3

>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'

f-string 调试信息

以前的写法如下:

1
log.info('verify session id:{}'.format(session_id))

现在的用法如下:

1
log.info(f'verify session id:{session_id}')

3.8 对 f-string 增加了 调试的信息,可以直接在其中使用 “=”。化。转换符 ‘!s’ 即对结果调用 str(),’!r’ 为调用 repr(),而 ‘!a’ 为调用 ascii()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>>x = 3
>>>print(f'{x*9 + 15=}')

>>> name = "Fred"
>>> f"He said his name is {name!r}."
"He said his name is 'Fred'."

>>> f"He said his name is {repr(name)}." # repr() 等于 !r
"He said his name is 'Fred'."

>>> width = 10
>>> precision = 4
>>> value = decimal.Decimal("12.34567")
>>> f"result: {value:{width}.{precision}}"
'result: 12.35'

>>> today = datetime(year=2017, month=1, day=27)
>>> f"{today:%B %d, %Y}"
'January 27, 2017'

>>> number = 1024
>>> f"{number:#0x}"
'0x400'

reversed

支持对 Dict 进行反转迭代

1
2
3
4
5
>>> for x in reversed({"a":"1", "b": "2"}):
... print(x)
...
b
a

lru_cache

lru_cache 是 Python3 中的新特性,这个装饰器实现了备忘的功能,是一项优化技术,把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。lru 是(least recently used)的缩写,即最近最少使用原则。表明缓存不会无限制增长,一段时间不用的缓存条目会被扔掉。以前的调用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import functools

import time

@functools.lru_cache()
def fib(n):
if n < 2:
return n
return fib(n - 2) + fib(n - 1)

start_time = time.time()
print(fib(100))
end_time = time.time()
print(f"time: {end_time- start_time}")

在 3.8 中 lru_cache 不再是 functools 返回装饰器的函数。而是可以直接使用

1
2
3
4
5
6
7
@lru_cache
def f(x):
...

@lru_cache(maxsize=256)
def f(x):
...

continue

由于实现问题,continue 语句在 finally 子句中是非法的。在 Python 3.8 中,这一限制被取消了。

1
2
3
4
5
6
7
8
for x in range(10):
try:
assert 5-x !=0
print(f"{x}")
except Exception as e:
print(f"{e}")
finally:
continue

在 3.7 中出现 SyntaxError: ‘continue’ not supported inside ‘finally’ clause。而在 3.8 中可以正常运行。

as_integer_ratio

bool, int 和 fractions.Fraction 类型现在都有一个 as_integer_ratio() 方法,与 float 和 decimal.Decimal 中的已有方法类似。

多进程共享内存

在 Python 3.8 中,multiprocessing 模块提供了 SharedMemory 类,可以在不同的 Python 进程之间创建共享的内存区域。

在旧版本的 Python 中,进程间共享数据只能通过写入文件、通过网络套接字发送,或采用 Python 的 pickle 模块进行序列化等方式。共享内存提供了进程间传递数据的更快的方式,从而使得 Python 的多处理器和多内核编程更有效率。

共享内存片段可以作为单纯的字节区域来分配,也可以作为不可修改的类似于列表的对象来分配,其中能保存数字类型、字符串、字节对象、None 对象等一小部分 Python 对象。详细信息见文档

Typing 模块的改进

Python 是动态类型语言,但可以通过 typing 模块添加类型提示,以便第三方工具验证 Python 代码。Python 3.8给 typing 添加了一些新元素,因此它能够支持更健壮的检查,详细信息见文档

  • final 修饰器和 Final 类型标注表明,被修饰或被标注的对象在任何时候都不应该被重写、继承,也不能被重新赋值。
  • Literal 类型将表达式限定为特定的值或值的列表(不一定是同一个类型的值)。
  • TypedDict 可以用来创建字典,其特定键的值被限制在一个或多个类型上。注意这些限制仅用于编译时确定值的合法性,而不能在运行时进行限制。

新版本的 pickle 协议

Python 的 pickle 模块提供了一种序列化和反序列化 Python 数据结构或实例的方法,可以将字典原样保存下来供以后读取。不同版本的 Python 支持的 pickle 协议不同,而最新版本的支持范围更广、更强大、更有效的序列化。

Python 3.8 引入的第 5 版 pickle 协议可以用一种新方法pickle对象,它能支持 Python 的缓冲区协议,如 bytes、memoryviews 或 Numpy array 等。新的 pickle 避免了许多在 pickle 这些对象时的内存复制操作。

NumPy、Apache Arrow 等外部库在各自的 Python绑定中支持新的 pickle协议。新的 pickle 也可以作为 Python 3.6 和 3.7 的插件使用,可以从 PyPI上安装。

性能改进

  • 许多内置方法和函数的速度都提高了 20%~50%,因为之前许多函数都需要进行不必要的参数转换。
  • 一个新的opcode缓存可以提高解释器中特定指令的速度。但是,目前实现了速度改进的只有 LOAD_GLOBAL opcode,其速度提高了40%。以后的版本中也会进行类似的优化。
  • 文件复制操作如 shutil.copyfile() 和 shutil.copytree() 现在使用平台特定的调用和其他优化措施,来提高操作速度。
  • 新创建的列表现在平均比以前小了12%,这要归功于列表构造函数如果能提前知道列表长度的情况下进行的优化。
  • Python 3.8 中向新型类(如class A(object))的类变量中的写入操作变得更快。operator.itemgetter() 和collections.namedtuple()也得到了速度优化。

Python C API和CPython实现

Python 最近的版本在 CPython(C语言编写的Python的参考实现)中使用的 C API 重构方面下了很大功夫。到目前为止这些工作还在不断添加,现有的成果包括:

  • Python 初始化配置(Python Initialization Configuration)有了个新的 C API,可以实现对 Python 初始化例程更紧密的控制和更详细的反馈。如此一来,将 Python 运行时嵌入到其他应用程序中就会更容易,也可以以编程方式给 Python 程序传递启动参数。新的 API 还确保了所有 Python 配置控制都有一个单一的、一致的位置,因此以后的改变(如 Python 的新的 UTF-8 模式)也更为容易。
  • CPython 的另一个新的 C API——“vectorcall” 调用协议——可以实现针对 Python 内部方法更快的调用,而无需创建临时对象。该 API 依然不稳定,但已有了明显的改善。该 API 计划在 Python 3.9 中成熟。
  • Python 运行时的审计钩子为 Python 运行时提供了两个 API,可以用来勾住事件,从而保证测试框架、日志和审计系统等外部工具能够监视到它们。

3.7

breakpoint

3.7 包含了新的内置 breakpoint() 函数,作为一种简单方便地进入 Python 调试器的方式。内置 breakpoint() 会调用 sys.breakpointhook()。 在默认情况下后者会导入 pdb 然后再调用 pdb.set_trace(),但是通过将 sys.breakpointhook() 绑定到你选定的函数,breakpoint() 可以进入任何调试器。 此外,环境变量 PYTHONBREAKPOINT 可被设置为你选定的调试器的可调用对象。 设置 PYTHONBREAKPOINT=0 会完全禁用内置 breakpoint()。

contextvars

上下文变量, 详细介绍

dataclasses

1
2
3
4
5
6
7
8
@dataclass
class Point:
x: float
y: float
z: float = 0.0

p = Point(1.5, 2.5)
print(p) # produces "Point(x=1.5, y=2.5, z=0.0)"

更多关于 dataclasses 和 attr 的介绍

纳秒级精度的新时间函数

1
2
3
4
5
6
time.clock_gettime_ns()
time.clock_settime_ns()
time.monotonic_ns()
time.perf_counter_ns()
time.process_time_ns()
time.time_ns()